/**
 * Copyright (C) 2008 aileron.cc
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.aileron.accessor;

import static cc.aileron.accessor.PojoAccessor.Type.*;
import static cc.aileron.commons.utils.CastUtils.*;

import java.util.HashMap;

import cc.aileron.accessor.properties.NumberConvertor;
import cc.aileron.accessor.properties.PojoProperties;
import cc.aileron.commons.instance.InstanceFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;

/**
 * 
 * PojoAccessorFactoryImpl
 * 
 * @author Aileron
 * 
 */
@Singleton
public class PojoAccessorFactoryImpl implements PojoAccessorFactory
{
    /*
     * (非 Javadoc)
     * 
     * @see cc.aileron.accessor.PojoAccessorFactory#create(java.lang.Object)
     */
    public <T> PojoAccessor<T> create(final T pojo)
    {
        return new PojoAccessor<T>()
        {
            @Override
            public Object get(final String key)
            {
                return get(key, Object.class);
            }

            public <R> R get(final String rowkey, final Class<R> type)
            {
                if (rowkey.indexOf('@') != 0)
                {
                    final Object result = getterChainAccessor(pojo, rowkey);
                    try
                    {
                        final Object convetValue = numberConvertor
                                .convertValue(type, result);
                        return type.cast(convetValue);
                    }
                    catch (final ClassCastException e)
                    {
                        throw new PojoAccessorCastError(pojo.getClass(),
                                rowkey, type);
                    }
                }

                final String key;
                final String convertorName;
                {
                    final int lastIdx = rowkey.lastIndexOf('@');
                    key = rowkey.substring(lastIdx + 1);
                    convertorName = rowkey.substring(1, lastIdx);
                }

                try
                {
                    final Object result = getterChainAccessor(pojo, key);
                    final PojoAccessorDxo.Getter<Object, R> conv = cast(instance
                            .create(Class.forName(convertorName)));
                    return conv.getter(result, type);
                }
                catch (final ClassNotFoundException e)
                {
                    throw new Error(e);
                }
            }

            @SuppressWarnings("unchecked")
            public <R> R get(final String key, final TypeLiteral<R> type)
            {
                return (R) get(key, Object.class);
            }

            @Override
            public PojoAccessor<Object> getAccessor(final String key)
            {
                final Object pojo = get(key, Object.class);
                return create(pojo);
            }

            public void set(final String rowkey, final Object value)
            {
                if (rowkey.indexOf('@') != 0)
                {
                    setterChainAccessor(pojo, rowkey, value);
                    return;
                }

                final String key;
                final String convertorName;
                {
                    final int lastIdx = rowkey.lastIndexOf('@');
                    key = rowkey.substring(lastIdx + 1);
                    convertorName = rowkey.substring(1, lastIdx);
                }
                try
                {
                    final PojoAccessorDxo.Setter<Object, Object> conv = cast(instance
                            .create(Class.forName(convertorName)));
                    final Class<Object> parameterType = typeChainAccessor(pojo,
                            key);
                    final Object convertParameter = conv.setter(value,
                            parameterType);
                    setterChainAccessor(pojo, key, convertParameter);
                }
                catch (final ClassNotFoundException e)
                {
                    throw new Error(e);
                }
            }

            public T toTarget()
            {
                return pojo;
            }

        };
    }

    Object getterChainAccessor(final Object pojo, final String key)
    {
        Object obj = pojo;
        PojoProperties<Object> pMap = getMap(obj);
        final String[] keys = key.split("\\.");
        final int max = keys.length;
        for (int i = 0; i < max; i++)
        {
            final String name = keys[i];
            final Object self = obj;
            obj = pMap.get(self, name);
            if (obj == null)
            {
                obj = instance.create(pMap.type(GET, name));
                pMap.set(self, name, obj);
            }
            pMap = getMap(obj);
        }
        return obj;
    }

    void setterChainAccessor(final Object pojo, final String key,
            final Object value)
    {
        Object obj = pojo;
        PojoProperties<Object> pMap = getMap(obj);
        final String[] keys = key.split("\\.");
        final int max = keys.length - 1;
        for (int i = 0; i < max; i++)
        {
            final Object self = obj;
            final String name = keys[i];
            obj = pMap.get(self, name);
            if (obj == null)
            {

                obj = instance.create(pMap.type(GET, name));
                pMap.set(self, name, obj);
            }
            pMap = getMap(obj);
        }
        pMap.set(obj, keys[max], value);
    }

    @SuppressWarnings("unchecked")
    Class<Object> typeChainAccessor(final Object pojo, final String key)
    {
        Object obj = pojo;
        PojoProperties<Object> pMap = getMap(obj);
        final String[] keys = key.split("\\.");
        final int max = keys.length - 1;
        for (int i = 0; i < max; i++)
        {
            final String name = keys[i];
            final Object self = obj;
            obj = pMap.get(self, name);
            if (obj == null)
            {
                obj = instance.create(pMap.type(GET, name));
                pMap.set(self, name, obj);
            }
            pMap = getMap(obj);
        }
        return (Class<Object>) pMap.type(SET, keys[max]);
    }

    private <T> PojoProperties<T> getMap(final T pojo)
    {
        final Class<T> targetClass = cast(pojo.getClass());
        final PojoProperties<T> pMap;
        if (map.containsKey(targetClass))
        {
            pMap = cast(map.get(targetClass));
        }
        else
        {
            pMap = PojoProperties.Factory.concreate(targetClass);
            map.put(targetClass, pMap);
        }
        return pMap;
    }

    /**
     * @param instance
     * @param numberConvertor
     */
    @Inject
    public PojoAccessorFactoryImpl(final InstanceFactory instance,
            final NumberConvertor numberConvertor)
    {
        this.instance = instance;
        this.numberConvertor = numberConvertor;
    }

    /**
     * instance-factory
     */
    final InstanceFactory instance;

    final NumberConvertor numberConvertor;

    /**
     * map
     */
    private final HashMap<Class<?>, PojoProperties<?>> map = new HashMap<Class<?>, PojoProperties<?>>();

}
